/*
 * LoadMap.java
 *
 * @author Michael Hansen
 * @author Lukas Thuernagel
 * @author Soeren Warszus
 */

package risk.io;

import java.awt.*;
import java.io.*;
import java.util.*;
import risk.game.WorldMap;
import risk.game.Continent;
import risk.game.Territory;

/**
 * Klasse zum Laden und Initialisieren von Spielkarten.
 * 
 * Die Spielkarten muessen in einem festen Format vorliegen.
 * Jeder Datei beginnt in der ersten Zeile mit einer Raute (#). Auf das Zeichen
 * kann ein Kommentar beliebiger Laenge folgen. Die zweite Zeile enthaelt 
 * Zusatzoptionen. Die dritte Zeile muss erneut mit einer Raute beginnen.
 * Ab der vierten Zeile beginnt die Liste der Kontinente. Pro Zeile steht nur
 * ein Kontinent und zwar im Format Kontinent_1 Truppenbonus &lt;Laenderliste&gt;.
 * Nach der Kontinentliste erfolgt wieder eine Raute in einer eigenen Zeile.
 * Anschliessend werden die Laender notiert. Pro Zeile darf erneu nur ein Land
 * stehen im Format: Land_1 X_Koordinate Y_Koordinate &lt;Liste der Nachbarn&gt;.
 * <br><br>
 * Ein (unvollstaendiges) Beispiel:
 * <br><br>
 * #<br>
 * #<br>
 * Erde myMap.jpg Nordamerika Suedamerika Europa Afrika Asien Australien<br>
 * #<br>
 * Nordamerika 5 Alaska Nordwest-Territorium Groenland Alberta Ontario<br>
 * Suedamerika 2 Venezuela Peru Brasilien Argentinien<br>
 * ...<br>
 * #<br>
 * Venezuela X1 Y1 Mittelamerika Peru Brasilien<br>
 * Peru X2 Y2 Venezuela Brasilien Argentinien<br>
 * Brasilien X3 Y3 Venezuela Peru Argentinien<br>
 * Argentinien X4 Y4 Peru Brasilien<br>
 * ...<br>
 * #
 */
public class LoadMap {
    /**
     *  Die spaeter erzeugte Instanz aus WorldMap, die durch das Parsen der
     *  Skriptdatei erzeugt wird.
     */
    WorldMap worldMap;
    
    /**
     * Der Konstruktor erzeugt die zum Spielen notwendige Weltkarte.
     *
     * Der Konstruktor liest die im Parameter angegebene Datei ein und erzeugt
     * aus der Skriptdatei Instanzen aus Continent, Terretory und WorldMap.
     *
     * @param file  Der Pfad zur zu ladenden Skriptdatei.
     */
    public LoadMap(String file) throws IllegalArgumentException {
        // benoetigte StringTokenizer, um die Datei zu parsen
        StringTokenizer tokenizer;
        
        // enthaelt den gesamten Inhalt der Skriptdatei als String
        String text;
       
        /* (temporaer) Listen der Kontinent- und Laenderinstanzen zum Abgleich
         * der einzelnen Listen innerhalb der Skriptdatei
         */
        Map<String, Territory> territoryList = new HashMap<String, Territory>();
        Map<String, Continent> continentList = new HashMap<String, Continent>();
        
        /* Listen mit den Kontinent- und Laenderinstanzen, die spaeter der
         * WorldMap hinzugefuegt werden
         */
        ArrayList wmTerritoryList = new ArrayList();
        ArrayList wmContinentList = new ArrayList();
        
        try {  
            // Datei einlesen
            text = readFile(file); 
            
            /* String anhand der Rautezeichen aufsplitten.
             * Hier erfolgt zunaechst eine Aufteilung in die Bereiche Optionen,
             * Metadaten (Grafik etc.), Kontinente und Laender
             */
            String[] block = text.split("#[^\n]*");
            
            /* Nur weitermachen, wenn die Anzahl der erwarteten Skriptdateiteile
             * vorhanden sind
             */
            if (block.length != 5) {
                throw new IllegalArgumentException("Das Laden der Skriptdatei" +
                        " schlug fehl! Andere Anzahl Blockbereiche erwartet!");
            }

            
            /************************ Block: Laender **************************/
            
            /* Wir beginnen zunaechst mit dem Anlegen der Laender-Instanzen.
             * Zunaechst muessen wir den String aber noch weiter aufsplitten.
             */
            tokenizer = new StringTokenizer(block[4], "\n");
            
            while(tokenizer.hasMoreTokens()) {
                String token = (String) tokenizer.nextToken().trim();
                
                // jede nicht-leere Zeile im Block durchlaufen
                if (!"".equals(token)) {
                    String[] options = token.split("<");
                    
                    /* Fehler werfen, wenn nicht die erwartete Anzahl an Parametern
                     * gefunden wurde. Leider ist bis hier das genaue Land unbekannt.
                     */
                    if (options.length != 3) {
                        throw new IllegalArgumentException("Ungueltige Anzahl an " +
                                "Parametern im Laenderblock entdeckt!");
                    }

                    // 1. Bereich der Zeile (Name und Mittelpunkt) weiter aufsplitten
                    String[] meta = (flattenSpace(options[0].trim())).split(" ");
                    
                    if (meta.length != 3) {
                        throw new IllegalArgumentException("Ungueltige Anzahl an " +
                                "Meta-Parametern im Laenderblock entdeckt!");
                    }

                    // Name des Landes
                    String name = meta[0].trim();
                    
                    // Landesmittelpunkt (Koordinaten auf der Weltkarte)
                    Point midpoint = new Point(Integer.parseInt(meta[1]), 
                                               Integer.parseInt(meta[2]));
                    
                    
                    // 2. Bereich (Landesgrenzen) der Zeile weiter aufsplitten
                    String coord = options[1].trim().substring(0, options[1].trim().length() - 1);
                    StringTokenizer coordinates = new StringTokenizer(coord, ",");
                    
                    
                    // nun die erhaltenen Tokens durchlaufen
                    int dim = (int) Math.round(coordinates.countTokens() / 2);
                    
                    int[] xCoordinates = new int[dim];
                    int[] yCoordinates = new int[dim];
                    int index = 0;
                    
                    while(coordinates.hasMoreTokens()) {
                        xCoordinates[index] = Integer.parseInt(coordinates.nextToken().trim());
                        yCoordinates[index] = Integer.parseInt(coordinates.nextToken().trim());
                        index++;
                    }

                    // Landesgrenzen
                    Polygon frontiers = new Polygon(xCoordinates, yCoordinates, index);
                    
                    // 3. Bereich (Nachbarlaender) der Zeile weiter aufsplitten
                    String strNeighbors = options[2].trim().substring(0, options[2].trim().length() - 1);
                    StringTokenizer neighbors = new StringTokenizer(strNeighbors, ",");
                    
                    ArrayList neighborList = new ArrayList();
                    String neighbor;
                    while(neighbors.hasMoreTokens()) {
                        neighbor = (String) neighbors.nextToken().trim();
                        if (!neighbor.equals(null)) {
                            neighborList.add(neighbor);    
                        }
                    }

                    // Land anlegen
                    Territory t = new Territory(name, frontiers, midpoint, neighborList);
                    
                    // Land zur Laenderliste hinzufuegen
                    territoryList.put(name, t);
                    
                } // end if (!" ".equals(token))
            } // end while(tokenizer.hasMoreTokens())
            
            
            
            /************************ Block: Kontinente ***********************/
            tokenizer = new StringTokenizer(block[3], "\n");
            
            while(tokenizer.hasMoreTokens()) {
                String token = (String) tokenizer.nextToken().trim();
                
                // jede nicht-leere Zeile im Block durchlaufen
                if (!"".equals(token)) {
                    String[] options = token.split("<");
                    
                    if (options.length != 2) {
                        throw new IllegalArgumentException("Ungueltige Anzahl an " +
                                "Parametern in einem Kontinentblock entdeckt!");
                    }
                    
                    // 1. Bereich der Zeile (Name und Truppenbonus) weiter aufsplitten
                    String[] meta = (flattenSpace(options[0].trim())).split(" ");
                    
                    if (meta.length != 2) {
                        throw new IllegalArgumentException("Ungueltige Anzahl an " +
                                "Meta-Parametern in einem Kontinentblock entdeckt!");
                    }

                    // Kontinentname
                    String name = meta[0];
                    
                    // Kontinentbonus
                    int bonus   = Integer.parseInt(meta[1]);
                    
                    // 2. Bereich (Laenderliste) der Zeile weiter aufsplitten
                    String strTerritories = options[1].trim().substring(0, options[1].trim().length() - 1);
                    StringTokenizer territories = new StringTokenizer(strTerritories, ",");
                    
                    // Liste aller zum Kontinent gehoerigen Laender
                    ArrayList continentTerritoryList = new ArrayList();
                    while(territories.hasMoreTokens()) {
                        String territory = territories.nextToken().trim();
                    
                        if (territoryList.containsKey(territory)) {
                            Territory t = (Territory) territoryList.get(territory);
                            /* Land wird ein einem Kontinent auch genutzt =>
                             * erst jetzt wirklich uebernehmen fuer die WorldMap.
                             */
                            wmTerritoryList.add(t);
                            
                            /* und in die zur Kontinent gehoerigen Laender-Liste
                             * aufnehmen.
                             */
                            continentTerritoryList.add(t);
                        } else {
                            throw new IllegalArgumentException("Die Laenderliste " +
                                    "im Kontinent " + name + " falsch. Das Land " +
                                    territory + " konnte nicht gefunden werden.");
                        }
                    }

                    // Kontinent anlegen
                    Continent c = new Continent(name, bonus, continentTerritoryList);
                    
                    // Kontinent zur Kontinentliste hinzufuegen
                    continentList.put(name, c);
                } // end if (!" ".equals(token))
            } // end while(tokenizer.hasMoreTokens())
                     
            
            /************************ Block: Metadaten ************************/
            String[] options = block[2].split("<");
            
            if (options.length != 2) {
                throw new IllegalArgumentException("Ungueltige Anzahl an " + 
                        "Parametern innerhalb des 2. Blocks entdeckt!");
            }
                    
            // 1. Bereich der Zeile (Name und Graphic) weiter aufsplitten
            String[] meta = (flattenSpace(options[0].trim())).split(" ");
                    
            if (meta.length != 2) {
                throw new IllegalArgumentException("Ungueltige Anzahl an " + 
                        "Parametern innerhalb des 2. Blocks entdeckt!");
            }

            String name    = meta[0];
            String graphic = meta[1];
                    
            // 2. Bereich (Kontinentliste) der Zeile weiter aufsplitten
            // zuvor das ">" entfernen
            String strContinents = options[1].trim().substring(0, options[1].trim().length() - 1);
            StringTokenizer continents = new StringTokenizer(strContinents, ",");
                    
            ArrayList continentTerritoryList = new ArrayList();
            while(continents.hasMoreTokens()) {
                String continent = continents.nextToken().trim();
                    
                if (continentList.containsKey(continent)) {
                    Continent c = (Continent) continentList.get(continent);
                    
                    /* Der Kontinent wird innerhalb der Welt auch genutzt =>
                     * erst jetzt wirklich fuer die WorldMap uebernehmen.
                     */
                     wmContinentList.add(c);
                } else {
                    throw new IllegalArgumentException("Die Kontinentliste " +
                            "zur Weltkarte ist falsch. Das Kontinent " +
                            continent + " konnte nicht gefunden werden.");
                }
            }
    
            
            /************************ Block: Optionen *************************/
            tokenizer = new StringTokenizer(block[1], " ");
                
            /* bisher sind keine Optionen bekannt. Daher keine weitere
               Behandlung solcher. */
            
            
            
            
            /* Wir sind fertig und erzeugen die Instanz von WorldMap, die fortan
             * alle ausgelesenen Daten enthaelt.
             */
            this.worldMap = new WorldMap(name, graphic, wmContinentList, wmTerritoryList);
            
        } catch (IOException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
    
    /**
     * Die Methode liest die Skriptdatei in einen String ein.
     *
     * @param filename  String mit Pfad zur Skriptdatei.
     */
    static String readFile(String filename) throws IOException {
        FileReader rd = new FileReader(filename);
        File f = new File(filename);
        long len = f.length();
       
        if (len > Integer.MAX_VALUE) {
            throw new IOException("Die angegebene Datei ist zu gross!");
        }
        
        StringBuffer buff = new StringBuffer((int) len);
        char[] chars = new char[(int) len];
        
        int charsRead = 0;
        
        while ((charsRead = rd.read(chars, 0, (int) len)) > 0 ) {
            buff.append(chars, 0, charsRead);
        }
        
        return buff.toString();
    }
    
    /**
     * Hilfsmethode um mehrere Leerzeichen innerhalb eines Strings auf ein 
     * Leerzeichen zu reduzieren
     *
     * @param s  Der "flachzuklopfende" String
     * @return   String ohne 2 aufeinander folgende Leerzeichen
     */
    
    private String flattenSpace(String s) {
        StringBuffer b = new StringBuffer(s.length());
        int nBlanks = 0;

        for (int i = 0; i < s.length(); i++){
            char c = s.charAt(i);
            
            if (c == ' ' || c == '\t') {
                nBlanks++;
            
                if (nBlanks < 2) {
                    b.append(c);
                }
            } else {
                nBlanks = 0;
                b.append(c);
            }
        }
        
        return b.toString().trim();
    }
    
    public WorldMap getWorldMap() {
        return this.worldMap;
    }
    
    
    
    public static void main(String[] args) {
        //LoadMap lm = new LoadMap("C:/java/ooprg/risk/risk/maps/standard.map");
        LoadMap lm = new LoadMap("/home/swa/Desktop/maps/risk_standard.map");
        WorldMap wm = lm.getWorldMap();
        
        ListIterator territories = wm.getTerritories().listIterator();
        
        System.out.println("******** Laenderliste********");
        while (territories.hasNext()) {
            String name = ((Territory) territories.next()).getName();
            System.out.println("Name: " + name);
        }
        
        
        ListIterator continents = wm.getContinents().listIterator();
        System.out.println("******** Kontinentliste ********");
        while (continents.hasNext()) {
            String name = ((Continent) continents.next()).getName();
            System.out.println("Name: " + name);
        }
    }
    
    public void test(String file){
        LoadMap lm = new LoadMap(file);
        WorldMap wm = lm.getWorldMap();
        
        ListIterator territories = wm.getTerritories().listIterator();
        
        System.out.println("******** Laenderliste********");
        while (territories.hasNext()) {
            String name = ((Territory) territories.next()).getName();
            System.out.println("Name: " + name);
        }
        
        
        ListIterator continents = wm.getContinents().listIterator();
        System.out.println("******** Kontinentliste ********");
        while (continents.hasNext()) {
            String name = ((Continent) continents.next()).getName();
            System.out.println("Name: " + name);
        }
    }
}
